﻿using System;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Reader.Chromely.Core.Host;
using Xilium.CefGlue.Platform.Windows;
using static Reader.Chromely.Native.WinNativeMethods;

namespace Reader.Chromely.Native
{
    public class FramelessWinAPIHost : WinAPIHost
    {
        private static readonly HitTestValue[] BorderHitTestResults =
        {
            // TODO: Resizing from the top border causes glitches.
            // HitTestValue.HTTOP,
            // HitTestValue.HTTOPLEFT,
            // HitTestValue.HTTOPRIGHT,
            HitTestValue.HTBOTTOM,
            HitTestValue.HTBOTTOMLEFT,
            HitTestValue.HTBOTTOMRIGHT,
            HitTestValue.HTLEFT,
            HitTestValue.HTRIGHT,
            HitTestValue.HTBORDER
        };

        private bool _maximized;

        protected override Tuple<WindowStyles, WindowExStyles, ShowWindowCommand> GetWindowStyles(WindowState state)
        {
            var baseStyles = base.GetWindowStyles(state);
            //var exStyles = WindowExStyles.WS_EX_STATICEDGE | WindowExStyles.WS_EX_APPWINDOW;
            //var styles = WindowStyles.WS_OVERLAPPEDWINDOW;
            var styles = WindowStyles.WS_POPUPWINDOW | WindowStyles.WS_CLIPCHILDREN | WindowStyles.WS_CLIPSIBLINGS
                | WindowStyles.WS_SIZEBOX | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX;
            return new Tuple<WindowStyles, WindowExStyles, ShowWindowCommand>(
                styles, baseStyles.Item2, baseStyles.Item3);
            //return new Tuple<WindowStyles, WindowExStyles, ShowWindowCommand>(styles, exStyles, baseStyles.Item3);
        }

        protected override IntPtr WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
        {
            WM msg = (WM)message;
            switch (msg)
            {
                case WM.CREATE:
                    _handle = hWnd;
                    break;
                case WM.NCPAINT:
                    return IntPtr.Zero;
                case WM.NCACTIVATE:
                    return DefWindowProc(hWnd, message, wParam, new IntPtr(-1));
                case WM.SIZE:
                    {
                        //_maximized = wParam == (IntPtr)WindowSizeFlag.SIZE_MAXIMIZED
                        //    || wParam ==(IntPtr)WindowSizeFlag.SIZE_MAXSHOW;
                        //ForceRedraw(hWnd);
                        
                        //break;
                        if(wParam == (IntPtr)WindowSizeFlag.SIZE_MAXIMIZED)
                        {
                            var monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
                            MONITORINFOEX info = new MONITORINFOEX();
                            info.Init();
                            GetMonitorInfo(monitor, ref info);

                            SetWindowPos(hWnd, IntPtr.Zero, info.workArea.left, info.workArea.top, info.workArea.right, info.workArea.bottom, SetWindowPosFlags.IgnoreZOrder);
                        }
                        break;
                    }
                case WM.NCCALCSIZE:
                    {
                        //var captionHeight = GetSystemMetrics(SystemMetrics.CYCAPTION);
                        //var menuHeight = GetSystemMetrics(SystemMetrics.CYMENU);
                        //var padderBorder = GetSystemMetrics(SystemMetrics.CXPADDEDBORDER);
                        //// NB: 'menuHeight' substraction probably may broke it if window will have a Title in a caption.
                        //var topFrameHeight = captionHeight - menuHeight + padderBorder;

                        //var result = DefWindowProc(hWnd, message, wParam, lParam);
                        //var csp = (NcCalcSizeParams)Marshal.PtrToStructure(lParam, typeof(NcCalcSizeParams));
                        //csp.Region.Input.TargetWindowRect.Top -= _maximized ? 0 : topFrameHeight;
                        //Marshal.StructureToPtr(csp, lParam, false);
                        //return result;
                        if(wParam == (IntPtr)1)
                        {
                            var param = (NcCalcSizeParams)Marshal.PtrToStructure(lParam, typeof(NcCalcSizeParams));
                            AdjustMaximizedClientRect(hWnd, ref param.Region.Input.TargetWindowRect);
                            return (IntPtr)1;
                        }
                        return DefWindowProc(hWnd, message, wParam, lParam);
                    }
                case WM.NCHITTEST:
                    {
                        var result = DefWindowProc(hWnd, message, wParam, lParam);
                        if (BorderHitTestResults.Contains((HitTestValue)result))
                        {
                            return result;
                        }

                        // TODO: Try to find out why this is not enough for drag window functionality.
                        return (IntPtr)HitTestValue.HTCAPTION;
                    }
            }
            
            return base.WndProc(hWnd, message, wParam, lParam);
        }

        private static void ForceRedraw(IntPtr hWnd)
        {
            //IntPtr hmon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
            //MONITORINFOEX monInfo = new MONITORINFOEX();
            //monInfo.Init();
            //GetMonitorInfo(hmon, ref monInfo);
            //SetWindowPos(hWnd, IntPtr.Zero, monInfo.workArea.left, monInfo.workArea.top,
            //    monInfo.workArea.right, monInfo.workArea.bottom, SetWindowPosFlags.IgnoreZOrder);
            SetWindowPos(hWnd, IntPtr.Zero,
                //monInfo.workArea.left,monInfo.workArea.top,monInfo.workArea.right,monInfo.workArea.bottom,
                0, 0, 0, 0,
                            SetWindowPosFlags.FrameChanged
                           | SetWindowPosFlags.IgnoreResize
                           | SetWindowPosFlags.IgnoreMove
                           | SetWindowPosFlags.DoNotRedraw
                           | SetWindowPosFlags.IgnoreZOrder
                           | SetWindowPosFlags.DoNotChangeOwnerZOrder);

        }
        private static bool IsMaximized(IntPtr hWnd)
        {
            WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
            if(!GetWindowPlacement(hWnd, ref placement))
            {
                return false;
            }
            return placement.ShowCmd == ShowWindowCommands.Maximized;
        }

        private static void AdjustMaximizedClientRect(IntPtr hWnd, ref WinNativeMethods.RECT rect)
        {
            if (!IsMaximized(hWnd))
            {
                return;
            }

            var monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
            if(monitor == IntPtr.Zero)
            {
                return;
            }

            MONITORINFOEX info = new MONITORINFOEX();
            info.Init();
            if(!GetMonitorInfo(monitor, ref info))
            {
                return;
            }

            rect.Left = info.workArea.left;
            rect.Right = info.workArea.right;
            rect.Top = info.workArea.top;
            rect.Bottom = info.workArea.bottom;
        }
    }
}